package log

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"
)

//ContextBaseAssembleFunc assemble function for ContextBase implement
type ContextBaseAssembleFunc func(*ContextBase) string

//defaultAssembleFunc is default assemble function
var defaultAssembleFunc = assembleAsLineSeparator

//assembleAsLineSeparator assemble log context as a line with separator
func assembleAsLineSeparator(ctx *ContextBase) string {
	//assemble time string
	ctx.Common.TimeFmt(ctx.buff, time.Now())

	//assemble level name
	ctx.buff.WriteString(ctx.Common.Sep)
	ctx.buff.WriteString(getLevelName(ctx.level))

	//assemble common prefix
	ctx.buff.WriteString(ctx.Common.PrefixStr)

	//assemble context prefix
	if len(ctx.prefix) != 0 {
		ctx.buff.WriteString(ctx.Common.Sep)
		ctx.buff.WriteString(strings.Join(ctx.prefix, ctx.Common.Sep))
	}

	//assemble fmt pairs
	for i := 0; i < len(ctx.fmtKey); i++ {
		ctx.buff.WriteString(ctx.Common.Sep)
		ctx.buff.WriteString(ctx.fmtKey[i])
		ctx.buff.WriteString(ctx.Common.SepFmt)
		fmt.Fprintf(ctx.buff, "%v", ctx.fmtVal[i])
	}

	//assemble fmt fields pairs
	for i := 0; i < len(ctx.fmtFieldKey); i++ {
		ctx.buff.WriteString(ctx.Common.Sep)
		ctx.buff.WriteString(ctx.fmtFieldKey[i])
		ctx.buff.WriteString(ctx.Common.SepFmt)
		ctx.fmtFieldVal[i].AssembleAsLineSeparator(ctx.buff, ctx.Common)
	}

	//assemble code line
	if ctx.Common.BCodeLine {
		ctx.buff.WriteString(ctx.Common.Sep)
		ctx.assembleCallLine()
	}

	//assemble normal messages
	for i := 0; i < len(ctx.segFmt); i++ {
		ctx.buff.WriteString(ctx.Common.Sep)
		fmt.Fprintf(ctx.buff, ctx.segFmt[i], ctx.segArgs[i]...)
	}

	return ctx.buff.String()
}

var (
	_jsonKeyTimeStr      = "t"
	_jsonKeyLogLevel     = "l"
	_jsonKeyCommonPrefix = "p"
	_jsonKeyCtxFmt       = "f"
	_jsonKeyCodeLine     = "c"
	_jsonKeyMessage      = "m"
)

//assembleAsJSON assemble log context as JSON string
func assembleAsJSON(ctx *ContextBase) string {
	buff := ctx.buff

	//json head {
	buff.WriteByte('{')

	//assemble time string
	ctx.buff.WriteByte('"')
	ctx.buff.WriteString(_jsonKeyTimeStr)
	ctx.buff.WriteByte('"')
	ctx.buff.WriteByte(':')
	ctx.buff.WriteByte('"')
	ctx.Common.TimeFmt(ctx.buff, time.Now())
	ctx.buff.WriteByte('"')
	ctx.buff.WriteByte(',')

	//assemble level name
	ctx.buff.WriteByte('"')
	ctx.buff.WriteString(_jsonKeyLogLevel)
	ctx.buff.WriteByte('"')
	ctx.buff.WriteByte(':')
	ctx.buff.WriteByte('"')
	ctx.buff.WriteString(getLevelName(ctx.level))
	ctx.buff.WriteByte('"')
	ctx.buff.WriteByte(',')

	//assemble prefix
	ctx.buff.WriteByte('"')
	ctx.buff.WriteString(_jsonKeyCommonPrefix)
	ctx.buff.WriteByte('"')
	ctx.buff.WriteByte(':')
	ctx.buff.WriteByte('[')
	for i := 0; i < len(ctx.Common.Prefixes); i++ {
		assembleValueAsJSON(ctx.buff, ctx.Common.Prefixes[i][1])
		if i < len(ctx.Common.Prefixes)-1 {
			ctx.buff.WriteByte(',')
		}
	}
	if len(ctx.Common.Prefixes) != 0 && len(ctx.prefix) != 0 {
		ctx.buff.WriteByte(',')
	}
	for i := 0; i < len(ctx.prefix); i++ {
		assembleValueAsJSON(ctx.buff, ctx.prefix[i])
		if i < len(ctx.prefix)-1 {
			ctx.buff.WriteByte(',')
		}
	}
	ctx.buff.WriteByte(']')
	ctx.buff.WriteByte(',')

	//assemble fmt pairs
	ctx.buff.WriteByte('"')
	ctx.buff.WriteString(_jsonKeyCtxFmt)
	ctx.buff.WriteByte('"')
	ctx.buff.WriteByte(':')
	ctx.buff.WriteByte('{')
	for i := 0; i < len(ctx.fmtKey); i++ {
		ctx.buff.WriteByte('"')
		ctx.buff.WriteString(ctx.fmtKey[i])
		ctx.buff.WriteByte('"')
		ctx.buff.WriteByte(':')
		assembleValueAsJSON(ctx.buff, ctx.fmtVal[i])
		if i < len(ctx.fmtKey)-1 {
			ctx.buff.WriteByte(',')
		}
	}
	if len(ctx.fmtKey) != 0 && len(ctx.fmtFieldKey) != 0 {
		ctx.buff.WriteByte(',')
	}
	for i := 0; i < len(ctx.fmtFieldKey); i++ {
		ctx.buff.WriteByte('"')
		ctx.buff.WriteString(ctx.fmtFieldKey[i])
		ctx.buff.WriteByte('"')
		ctx.buff.WriteByte(':')
		ctx.fmtFieldVal[i].AssembleAsJSON(ctx.buff, ctx.Common)
		if i < len(ctx.fmtFieldKey)-1 {
			ctx.buff.WriteByte(',')
		}
	}
	ctx.buff.WriteByte('}')
	ctx.buff.WriteByte(',')

	//assemble code line
	if ctx.Common.BCodeLine {
		ctx.buff.WriteByte('"')
		ctx.buff.WriteString(_jsonKeyCodeLine)
		ctx.buff.WriteByte('"')
		ctx.buff.WriteByte(':')
		ctx.buff.WriteByte('"')
		ctx.assembleCallLine()
		ctx.buff.WriteByte('"')
		ctx.buff.WriteByte(',')
	}

	//assemble normal messages
	ctx.buff.WriteByte('"')
	ctx.buff.WriteString(_jsonKeyMessage)
	ctx.buff.WriteByte('"')
	ctx.buff.WriteByte(':')
	ctx.buff.WriteByte('[')
	for i := 0; i < len(ctx.segFmt); i++ {
		assembleValueAsJSON(ctx.buff, fmt.Sprintf(ctx.segFmt[i], ctx.segArgs[i]...))
		if i < len(ctx.segFmt)-1 {
			ctx.buff.WriteByte(',')
		}
	}
	ctx.buff.WriteByte(']')

	//json head }
	buff.WriteByte('}')

	return buff.String()
}

func assembleValueAsJSON(buff *strings.Builder, v interface{}) {
	bs, _ := json.Marshal(v)
	buff.Write(bs)
}
